/*
 * Copyright (C) 2010,2011.
 * AHCP Project
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.jacp.demo.service;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.VolatileImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.imageio.ImageIO;

import org.jacp.api.action.IAction;
import org.jacp.api.action.IActionListener;
import org.jacp.demo.util.ActionType;
import org.jacp.demo.util.DropShadowPanel;
import org.jacp.demo.util.FolderOpenAction;
import org.jacp.demo.util.ImageCacheAction;
import org.jacp.demo.util.ImageFileFilter;
import org.jacp.swing.rcp.component.AStatelessComponent;
import org.jdesktop.swingx.graphics.GraphicsUtilities;

/**
 * this directory service scans all image files recursive; and state less
 * component scales up to a max. available thread count
 * 
 * @author Andy Moncsek
 * 
 */
public class DirScanningService extends AStatelessComponent {

	private static VolatileImage placeHolder;
	private static VolatileImage clearPixel;

	@Override
	public Object handleAction(final IAction<ActionEvent, Object> action) {
		final Object message = action.getLastMessage();
		final List<DropShadowPanel> myPictures = new CopyOnWriteArrayList<DropShadowPanel>();
		final List<String> myImages = new CopyOnWriteArrayList<String>();
		if (message instanceof FolderOpenAction) {
			final FolderOpenAction myAction = (FolderOpenAction) message;
			File dir = null;
			if (myAction.getType() == org.jacp.demo.util.FolderOpenAction.ActionType.SCAN) {
				// recursive action
				dir = myAction.getPath() != null ? new File(myAction.getPath())
						: myAction.getFolderToOpen();
				scan(dir, myPictures, myImages);

			} else if (myAction.getType() == org.jacp.demo.util.FolderOpenAction.ActionType.BROWSE) {
				// get only files in current directory
				dir = myAction.getPath() != null ? new File(myAction.getPath())
						: myAction.getFolderToOpen();
				final File[] files = dir.listFiles(new ImageFileFilter());
				for (int i=0;i<files.length;i++) {
				    	final File file = files[i];
					addToCollection(myImages, myPictures, new DropShadowPanel(
							getPlaceHolder(),getClearPixel(), file.getAbsolutePath()));
				}

			}
			final File[] files = dir!=null?dir.listFiles(new ImageFileFilter()):new File[]{};
			if(files.length>0) {
				sendMessageToCacheService(new ImageCacheAction(dir
						.getAbsolutePath()!=null?dir
						.getAbsolutePath():"", ActionType.PARALLEL, myImages));
			}


			setHandleTarget("id01.id02");
			return myPictures;
		}

		return null;
	}
	/**
	 * access from different threads!!
	 * @return
	 */
	private synchronized final VolatileImage getPlaceHolder() {		
		if(placeHolder==null) {
			final BufferedImage myPicture = getPlaceHolderImage();
			final BufferedImage shadow = refreshShadow(myPicture);
			placeHolder = createFinalImage(shadow, myPicture);
		}
		
		return placeHolder;
	}
	
	/**
	 * access from different threads!!
	 * @return
	 */
	private synchronized final VolatileImage getClearPixel() {
		if(clearPixel==null) {
			clearPixel = createClearPixel();
		}		
		return clearPixel;
	}
	
	private final VolatileImage createClearPixel() {
		final VolatileImage finalImage = createVolatileImage(1, 1, Transparency.BITMASK);
		final Graphics offscreenGraphics = finalImage.getGraphics();
		((Graphics2D) offscreenGraphics).setBackground(new Color(0, 0, 0, 0));
		offscreenGraphics.clearRect(0, 0, 1,1);

		offscreenGraphics.dispose();

		return finalImage;
	}
	
	private final VolatileImage createFinalImage(final BufferedImage shadow,
			final BufferedImage original) {
		final VolatileImage finalImage = createVolatileImage(original
				.getWidth() + 15, original.getHeight() + 15,
				Transparency.TRANSLUCENT);
		final Graphics offscreenGraphics = finalImage.getGraphics();

		((Graphics2D) offscreenGraphics).setComposite(AlphaComposite.Src);

		((Graphics2D) offscreenGraphics).setBackground(new Color(0, 0, 0, 0));
		offscreenGraphics.clearRect(0, 0, finalImage.getWidth(), finalImage
				.getHeight());

		if (shadow != null) {
			final int x = (finalImage.getWidth() - shadow.getWidth()) / 2;
			final int y = (finalImage.getHeight() - shadow.getHeight()) / 2;
			offscreenGraphics.drawImage(shadow, x + DropShadowPanel.distance_x,
					y + DropShadowPanel.distance_y, null);
		}

		final int x = (finalImage.getWidth() - original.getWidth()) / 2;
		final int y = (finalImage.getHeight() - original.getHeight()) / 2;
		offscreenGraphics.drawImage(original, x, y, null);

		offscreenGraphics.dispose();

		return finalImage;
	}
	
	/**
	 * create HW accelerated image
	 * @param width
	 * @param height
	 * @param transparency
	 * @return
	 */
	private final VolatileImage createVolatileImage(int width, int height,
			int transparency) {
		final GraphicsEnvironment ge = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		final GraphicsConfiguration gc = ge.getDefaultScreenDevice()
				.getDefaultConfiguration();
		VolatileImage image = null;

		image = gc.createCompatibleVolatileImage(width, height, transparency);

		int valid = image.validate(gc);

		if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
			image = this.createVolatileImage(width, height, transparency);
			return image;
		}

		return image;
	}
	
	private final BufferedImage refreshShadow(final BufferedImage original) {
		if (original != null) {
			final String key = original.getWidth() + "+" + original.getHeight();
			BufferedImage shadowTemp = DropShadowPanel.shadowCache.get(key);
			if (shadowTemp == null) {
				shadowTemp = createDropShadow(original);
				DropShadowPanel.shadowCache.put(key, shadowTemp);
			}
			return shadowTemp;
		}
		return null;
	}
	
	/**
	 * create shadow for an image
	 * 
	 * @param image
	 * @return
	 */
	private final BufferedImage createDropShadow(final BufferedImage image) {
		final BufferedImage subject = prepareImage(image);
		applyShadow(subject);
		return subject;
	}

	/**
	 * create transparent image int ARGB format
	 * 
	 * @param image
	 * @return
	 */
	private final BufferedImage prepareImage(final BufferedImage image) {
		final BufferedImage subject = new BufferedImage(image.getWidth()
				+ DropShadowPanel.shadowSize * 2, image.getHeight()
				+ DropShadowPanel.shadowSize * 2, BufferedImage.TYPE_INT_ARGB);

		final Graphics2D g2 = subject.createGraphics();
		g2.drawImage(image, null, DropShadowPanel.shadowSize,
				DropShadowPanel.shadowSize);
		g2.dispose();

		return subject;
	}

	/**
	 * compute shadow
	 * 
	 * @param image
	 */
	private final void applyShadow(final BufferedImage image) {
		final int dstWidth = image.getWidth();
		final int dstHeight = image.getHeight();

		final int left = (DropShadowPanel.shadowSize - 1) >> 1;
		final int right = DropShadowPanel.shadowSize - left;
		final int xStart = left;
		final int xStop = dstWidth - right;
		final int yStart = left;
		final int yStop = dstHeight - right;

		final int shadowRgb = DropShadowPanel.shadowColor.getRGB() & 0x00FFFFFF;

		final int[] aHistory = new int[DropShadowPanel.shadowSize];
		int historyIdx = 0;

		int aSum;

		final int[] dataBuffer = ((DataBufferInt) image.getRaster()
				.getDataBuffer()).getData();
		final int lastPixelOffset = right * dstWidth;
		final float sumDivider = DropShadowPanel.shadowOpacity
				/ DropShadowPanel.shadowSize;

		// horizontal pass

		for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y
				* dstWidth) {
			aSum = 0;
			historyIdx = 0;
			for (int x = 0; x < DropShadowPanel.shadowSize; x++, bufferOffset++) {
				final int a = dataBuffer[bufferOffset] >>> 24;
				aHistory[x] = a;
				aSum += a;
			}

			bufferOffset -= right;

			for (int x = xStart; x < xStop; x++, bufferOffset++) {
				int a = (int) (aSum * sumDivider);
				dataBuffer[bufferOffset] = a << 24 | shadowRgb;

				// substract the oldest pixel from the sum
				aSum -= aHistory[historyIdx];

				// get the lastest pixel
				a = dataBuffer[bufferOffset + right] >>> 24;
				aHistory[historyIdx] = a;
				aSum += a;

				if (++historyIdx >= DropShadowPanel.shadowSize) {
					historyIdx -= DropShadowPanel.shadowSize;
				}
			}
		}

		// vertical pass
		for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
			aSum = 0;
			historyIdx = 0;
			for (int y = 0; y < DropShadowPanel.shadowSize; y++, bufferOffset += dstWidth) {
				final int a = dataBuffer[bufferOffset] >>> 24;
				aHistory[y] = a;
				aSum += a;
			}

			bufferOffset -= lastPixelOffset;

			for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
				int a = (int) (aSum * sumDivider);
				dataBuffer[bufferOffset] = a << 24 | shadowRgb;

				// substract the oldest pixel from the sum
				aSum -= aHistory[historyIdx];

				// get the lastest pixel
				a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
				aHistory[historyIdx] = a;
				aSum += a;

				if (++historyIdx >= DropShadowPanel.shadowSize) {
					historyIdx -= DropShadowPanel.shadowSize;
				}
			}
		}
	}

	private BufferedImage getPlaceHolderImage() {
		try {
			BufferedImage paceHolderImage = ImageIO.read(new File(
					"images/jpgPlaceholder.png"));
			// 88 vs 66 -> 4:3
			if (paceHolderImage.getWidth() > DropShadowPanel.THUMB_WIDTH
					&& paceHolderImage.getHeight() > DropShadowPanel.THUMB_HEIGHT) {
				// check landscape or portrait
				final double direction = (double) paceHolderImage.getWidth()
						/ (double) paceHolderImage.getHeight();
				if (direction > 1) {
					paceHolderImage = GraphicsUtilities.createThumbnail(
							paceHolderImage,
							(int) (DropShadowPanel.THUMB_HEIGHT * direction),
							DropShadowPanel.THUMB_HEIGHT);
				} else if (direction < 1) {
					paceHolderImage = GraphicsUtilities.createThumbnail(
							paceHolderImage,
							(int) (DropShadowPanel.THUMB_WIDTH * direction),
							DropShadowPanel.THUMB_WIDTH);
				} else {
					paceHolderImage = GraphicsUtilities.createThumbnail(
							paceHolderImage, DropShadowPanel.THUMB_WIDTH,
							DropShadowPanel.THUMB_WIDTH);
				}

			}
			return paceHolderImage;
		} catch (final IOException e) {
			e.printStackTrace();
			return null;
		}

	}

	private void sendMessageToCacheService(final ImageCacheAction action) {
		final IActionListener<ActionListener, ActionEvent, Object> listener = getActionListener();
		listener.getAction().addMessage("id01.id09", action);
		listener.getListener().actionPerformed(
				listener.getAction().getActionEvent());
	}

	private void scan(final File dir, final List<DropShadowPanel> myPictures,
			final List<String> myImages) {
		if (dir.isDirectory()) {
			final File[] files = dir.listFiles();
			if (files!=null) {
				for (final File file : files) {
					if (file.isDirectory() && !file.getName().equals("cache")) {
						// recursive call
						final IActionListener<ActionListener, ActionEvent, Object> listener = getActionListener();
						listener.getAction().setMessage(
								new org.jacp.demo.util.FolderOpenAction(file,
										FolderOpenAction.ActionType.SCAN));
						listener.getListener().actionPerformed(
								listener.getAction().getActionEvent());
					} else {
						if (file.getName().toLowerCase().endsWith(".jpg")) {
							addToCollection(
									myImages,
									myPictures,
									new DropShadowPanel(getPlaceHolder(),
											getClearPixel(), file
													.getAbsolutePath()));
						}
					}
				}
			}
		} else {
			addToCollection(myImages, myPictures, new DropShadowPanel(getPlaceHolder(),getClearPixel(), dir
					.getAbsolutePath()));

		}
	}

	private void addToCollection(final List<String> myImages,
			final List<DropShadowPanel> myPictures, final DropShadowPanel panel) {
		myPictures.add(panel);
		myImages.add(panel.getFilePath());
	}

}
